package com.cskaoyan.javase.oop3._2inner._5lambda._0introduction;

/**
 * Lambda表达式
 * Lambda 表达式是 JDK8 的一个新特性，可以取代接口的匿名内部类，写出更优雅的Java 代码。
 * 如果说匿名内部类实际上是局部内部类的更进一步，简化了局部内部类，那么Lambda就是匿名内部类更进一步，语法上更简洁了，代码更优雅了，是高端的玩法，是人上人的玩法。
 *
 * 以上总结:
 *      1.Lambda表达式也是特殊的局部内部类,它同样有局部内部类的那条注意事项.它仍然定义在方法等局部位置
 *          被作用域限制访问范围
 *      2.Lambda表达式可以取代接口的匿名内部类,所以这一点告诉我们,Lambda表达式是特殊的匿名内部类
 *          匿名内部类本质是一个对象,Lambda表达式本质也是一个对象
 *          而且Lambda表达式获取的是接口的子类对象
 *      3.Lambda表达式是比匿名内部类更加简化的语法。
 *
 *
 * 接下来我们开始学习Lambda表达式的使用
 * 1.首先，我们要知道Lambda表达式可以取代接口的匿名内部类，但是并不是什么接口都能够用Lambda表达式写出子类对象
 *      Lambda表达式对接口有特殊的要求
 *      在Java中，把能够使用Lambda表达式创建子类对象的接口称之为“功能接口（FunctionalInterface）”
 *      在语法上可以用注解“@FunctionalInterface”标注某个接口，如果它是功能接口不能语法报错，相反就会
 *      什么是功能接口呢？
 *          有且仅有一个必须要子类实现的抽象方法的接口，就是功能接口
 *
 *     思考：
 *        1.功能接口中仅能有一个方法吗？
 *          不是，Java8之后，接口中允许存在实现方法，他们不需要子类实现。
 *          接口中有实现方法，不影响功能接口
 *
 *        2.功能接口中仅能有一个抽象方法吗？
 *          不是，因为有些抽象方法不需要强制子类实现
 *          比如抽象方法：boolean equals(Object obj);
 *          接口的子类可以把Object类当中继承过来的实现，作为该抽象方法的实现
 *          于是抽象方法equals不需要子类强制实现，因为已经有Object类当中的实现了
 *
 *      当然，多数情况下，功能接口中仅有一个抽象方法。
 *
 * 功能接口存在后，就可以写Lambda表达式的语法来创建功能接口的子类对象。
 * Lambda表达式的语法：
 *      在局部位置写：
 *      () -> {}
 * 解释一下:
 *      () 表示功能接口中,那个必须被重写的抽象方法的形参列表 (把抽象方法的形参列表完完整整抄下来)
 *      -> 这个箭头就是Lambda表达式的运算符,它可以读作"goes to"
 *      {} 表示功能接口中,那个必须被重写的抽象方法的重写方法体 (想怎么写就怎么写)
 *
 * 思考:
 *      为什么功能接口中,要求仅有一个必须要实现的抽象方法?
 *      因为Lambda表达式的语法只能重写一个抽象方法
 *      而且{}表示重写方法的方法体,所以Lambda表达式所代表的子类对象不能新增独属于子类的成员
 *
 * 语法知道了,我们直接在方法中,写Lambda表达式的语法
 * 直接写Lambda表达式语法,显然是会编译报错的
 * 这是因为编译器无法直接从Lambda表达式的语法中得知:
 * 它究竟是哪个接口的子类对象,而java是强类型语言,任何对象变量都需要有类型,没有类型肯定会编译报错.
 *
 * 所以写Lambda表达式,需要一个额外的步骤,用来帮助编译器去了解,获取Lambda表达式的对象类型
 * 这一步称之为"Lambda表达式的类型推断"
 * 推断的过程需要借助上下文的信息,来完成类型推断(需要额外信息,帮助编译器理解Lambda表达式的类型)
 * Lambda表达式的类型推断,大致用四种常见方法:
 *      1.最直接的,用功能接口的引用指向它.
 *          有引用后,直接用引用名点调用成员方法即可
 *          而且由于Lambda表达式没有自身独有成员,所以也不担心无法访问子类独有成员的问题了.
 *      2.不接收,但是使用语法告诉编译器Lambda表达式的数据类型.
 *          语法:
 *          (接口名)Lambda表达式
 *          这种方式需要直接调用方法,不能直接写在那里:
 *          ((接口名)Lambda表达式).方法名(实参);
 *
 *       3.借助方法的返回值类型来完成类型推断.
 *       4.借助方法的形参数据类型来完成类型推断.
 *
 * 以上,Lambda表达式的基本使用就结束了
 * 但是Lambda表达式还可以继续简化.
 *
 *
 * @since 17:08
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        // 对照功能接口IB写Lambda表达式
        IB ib = () -> {
            System.out.println("hello world!");
        };
        ib.test();
        IA ia = () -> {
            System.out.println("你好世界!");
        };
        ia.test();

        ((IA) () -> {
            System.out.println("hello tom!");
        }).test();

        method().test();

        method(new IB(){
            @Override
            public void test() {
                System.out.println("我是匿名内部类的实现!");
            }
        });

        method(() -> {
            System.out.println("我是Lambda表达式的实现!我很优雅.");
        });
    }

    public static IA method() {
        // 匿名内部类的形式
        /*return new IA() {
            @Override
            public void test() {
                System.out.println("我是匿名内部类的实现!");
            }
        };*/
        // Lambda表达式的形式
        return () ->{
            System.out.println("我是Lambda表达式的实现!我更加简洁,高端!");
        };
    }

    public static void method(IB ib){
        ib.test();
    }
}

@FunctionalInterface
interface IB {
    void test();
}

@FunctionalInterface
interface IA {
    void test();

    default void testDefault() {
    }

    static void testStatic() {
    }

    boolean equals(Object obj);
}

class IAImpl implements IA {

    @Override
    public void test() {
        System.out.println("hello world!");
    }
}